--- Day 1: Inverse Captcha ---

The night before Christmas, one of Santa's Elves calls you in a panic. "The printer's broken! We can't print the Naughty or Nice List!" By the time you make it to sub-basement 17, there are only a few minutes until midnight. "We have a big problem," she says; "there must be almost fifty bugs in this system, but nothing else can print The List. Stand in this square, quick! There's no time to explain; if you can convince them to pay you in stars, you'll be able to--" She pulls a lever and the world goes blurry.

When your eyes can focus again, everything seems a lot more pixelated than before. She must have sent you inside the computer! You check the system clock: 25 milliseconds until midnight. With that much time, you should be able to collect all fifty stars by December 25th.

Collect stars by solving puzzles. Two puzzles will be made available on each day millisecond in the advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck!

You're standing in a room with "digitization quarantine" written in LEDs along one wall. The only door is locked, but it includes a small interface. "Restricted Area - Strictly No Digitized Users Allowed."

It goes on to explain that you may only leave by solving a captcha to prove you're not a human. Apparently, you only get one millisecond to solve the captcha: too fast for a normal human, but it feels like hours to you.

The captcha requires you to review a sequence of digits (your puzzle input) and find the sum of all digits that match the next digit in the list. The list is circular, so the digit after the last digit is the first digit in the list.

For example:

1122 produces a sum of 3 (1 + 2) because the first digit (1) matches the second digit and the third digit (2) matches the fourth digit.
1111 produces 4 because each digit (all 1) matches the next.
1234 produces 0 because no digit matches the next.
91212129 produces 9 because the only digit that matches the next one is the last digit, 9.

What is the solution to your captcha?

Your puzzle answer was 1393. --- Part Two ---

You notice a progress bar that jumps to 50% completion. Apparently, the door isn't yet satisfied, but it did emit a star as encouragement. The instructions change:

Now, instead of considering the next digit, it wants you to consider the digit halfway around the circular list. That is, if your list contains 10 items, only include a digit in your sum if the digit 10/2 = 5 steps forward matches it. Fortunately, your list has an even number of elements.

For example:

1212 produces 6: the list contains 4 items, and all four digits match the digit 2 items ahead.
1221 produces 0, because every comparison is between a 1 and a 2.
123425 produces 4, because both 2s match each other, but no other digit has a match.
123123 produces 12.
12131415 produces 4.

What is the solution to your new captcha?

Your puzzle answer was 1292.


In [28]:
import re

def solve(arr):
    n = len(arr)
    k = n >> 1
    res = 1
    for i in range(n):
        jmp = (i + k) % n
        if arr[i] == arr[jmp]:
            res += arr[i]
    return res

input = "5994521226795838486188872189952551475352929145357284983463678944777228139398117649129843853837124228353689551178129353548331779783742915361343229141538334688254819714813664439268791978215553677772838853328835345484711229767477729948473391228776486456686265114875686536926498634495695692252159373971631543594656954494117149294648876661157534851938933954787612146436571183144494679952452325989212481219139686138139314915852774628718443532415524776642877131763359413822986619312862889689472397776968662148753187767793762654133429349515324333877787925465541588584988827136676376128887819161672467142579261995482731878979284573246533688835226352691122169847832943513758924194232345988726741789247379184319782387757613138742817826316376233443521857881678228694863681971445442663251423184177628977899963919997529468354953548612966699526718649132789922584524556697715133163376463256225181833257692821331665532681288216949451276844419154245423434141834913951854551253339785533395949815115622811565999252555234944554473912359674379862182425695187593452363724591541992766651311175217218144998691121856882973825162368564156726989939993412963536831593196997676992942673571336164535927371229823236937293782396318237879715612956317715187757397815346635454412183198642637577528632393813964514681344162814122588795865169788121655353319233798811796765852443424783552419541481132132344487835757888468196543736833342945718867855493422435511348343711311624399744482832385998592864795271972577548584967433917322296752992127719964453376414665576196829945664941856493768794911984537445227285657716317974649417586528395488789946689914972732288276665356179889783557481819454699354317555417691494844812852232551189751386484638428296871436139489616192954267794441256929783839652519285835238736142997245189363849356454645663151314124885661919451447628964996797247781196891787171648169427894282768776275689124191811751135567692313571663637214298625367655969575699851121381872872875774999172839521617845847358966264291175387374464425566514426499166813392768677233356646752273398541814142523651415521363267414564886379863699323887278761615927993953372779567675"

if __name__ == '__main__':
    arr = []
    for char in input:
        arr.append(int(char))
print(solve(arr))

# Second part

s = 0
temp = 0
offset = int(input.__len__()/2)

for i in range(0, offset):
    if input[i]==input[(offset+i)  % (input.__len__())]:
        s += int(input[i]) + int(input[i])

print("Second part: " + str(s))


1293
Second part: 1292

Day 6

A debugger program here is having an issue: it is trying to repair a memory reallocation routine, but it keeps getting stuck in an infinite loop.

In this area, there are sixteen memory banks; each memory bank can hold any number of blocks. The goal of the reallocation routine is to balance the blocks between the memory banks.

The reallocation routine operates in cycles. In each cycle, it finds the memory bank with the most blocks (ties won by the lowest-numbered memory bank) and redistributes those blocks among the banks. To do this, it removes all of the blocks from the selected bank, then moves to the next (by index) memory bank and inserts one of the blocks. It continues doing this until it runs out of blocks; if it reaches the last memory bank, it wraps around to the first one.

The debugger would like to know how many redistributions can be done before a blocks-in-banks configuration is produced that has been seen before.

For example, imagine a scenario with only four memory banks:

The banks start with 0, 2, 7, and 0 blocks. The third bank has the most blocks, so it is chosen for redistribution.
Starting with the next bank (the fourth bank) and then continuing to the first bank, the second bank, and so on, the 7 blocks are spread out over the memory banks. The fourth, first, and second banks get two blocks each, and the third bank gets one back. The final result looks like this: 2 4 1 2.
Next, the second bank is chosen because it contains the most blocks (four). Because there are four memory banks, each gets one block. The result is: 3 1 2 3.
Now, there is a tie between the first and fourth memory banks, both of which have three blocks. The first bank wins the tie, and its three blocks are distributed evenly over the other three banks, leaving it with none: 0 2 3 4.
The fourth bank is chosen, and its four blocks are distributed such that each of the four banks receives one: 1 3 4 1.
The third bank is chosen, and the same thing happens: 2 4 1 2.

At this point, we've reached a state we've seen before: 2 4 1 2 was already seen. The infinite loop is detected after the fifth block redistribution cycle, and so the answer in this example is 5.

Given the initial block counts in your puzzle input, how many redistribution cycles must be completed before a configuration is produced that has been seen before? --- Part Two ---

Out of curiosity, the debugger would also like to know the size of the loop: starting from a state that has already been seen, how many block redistribution cycles must be performed before that same state is seen again?

In the example above, 2 4 1 2 is seen again after four cycles, and so the answer in that example would be 4.

How many cycles are in the infinite loop that arises from the configuration in your puzzle input?


In [44]:
blocks=[0,5,10,0,11,14,13,4,11,8,8,7,1,4,12,11]

def solve(blocks):
    curr = tuple(blocks)
    seen = set()
    while curr not in seen:
        seen.add(curr)
        im, iv = 0, blocks[0]
        for i, v in enumerate(blocks):
            if v > iv:
                im = i
                iv = v
        blocks[im] = 0
        while iv > 0:
            im = (im + 1) % len(blocks)
            blocks[im] += 1
            iv -= 1
        curr = tuple(blocks)
    return len(seen)

print(solve(blocks))
print(solve(blocks))


7864
1695

--- Day 7: Recursive Circus ---

Wandering further through the circuits of the computer, you come upon a tower of programs that have gotten themselves into a bit of trouble. A recursive algorithm has gotten out of hand, and now they're balanced precariously in a large tower.

One program at the bottom supports the entire tower. It's holding a large disc, and on the disc are balanced several more sub-towers. At the bottom of these sub-towers, standing on the bottom disc, are other programs, each holding their own disc, and so on. At the very tops of these sub-sub-sub-...-towers, many programs stand simply keeping the disc below them balanced but with no disc of their own.

You offer to help, but first you need to understand the structure of these towers. You ask each program to yell out their name, their weight, and (if they're holding a disc) the names of the programs immediately above them balancing on that disc. You write this information down (your puzzle input). Unfortunately, in their panic, they don't do this in an orderly fashion; by the time you're done, you're not sure which program gave which information.

For example, if your list is the following:

pbga (66) xhth (57) ebii (61) havc (66) ktlj (57) fwft (72) -> ktlj, cntj, xhth qoyq (66) padx (45) -> pbga, havc, qoyq tknk (41) -> ugml, padx, fwft jptl (61) ugml (68) -> gyxo, ebii, jptl gyxo (61) cntj (57)

...then you would be able to recreate the structure of the towers that looks like this:

            gyxo
          /     
     ugml - ebii
   /      \     
  |         jptl
  |        
  |         pbga
 /        /

tknk --- padx - havc \ \ | qoyq |
| ktlj \ /
fwft - cntj \
xhth

In this example, tknk is at the bottom of the tower (the bottom program), and is holding up ugml, padx, and fwft. Those programs are, in turn, holding up other programs; in this example, none of those programs are holding up any other programs, and are all the tops of their own towers. (The actual tower balancing in front of you is much larger.)

Before you're ready to help them, you need to make sure your information is correct. What is the name of the bottom program? Your puzzle answer was ahnofa.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

The programs explain the situation: they can't get down. Rather, they could get down, if they weren't expending all of their energy trying to keep the tower balanced. Apparently, one program has the wrong weight, and until it's fixed, they're stuck here.

For any program holding a disc, each program standing on that disc forms a sub-tower. Each of those sub-towers are supposed to be the same weight, or the disc itself isn't balanced. The weight of a tower is the sum of the weights of the programs in that tower.

In the example above, this means that for ugml's disc to be balanced, gyxo, ebii, and jptl must all have the same weight, and they do: 61.

However, for tknk to be balanced, each of the programs standing on its disc and all programs above it must each match. This means that the following sums must all be the same:

ugml + (gyxo + ebii + jptl) = 68 + (61 + 61 + 61) = 251
padx + (pbga + havc + qoyq) = 45 + (66 + 66 + 66) = 243
fwft + (ktlj + cntj + xhth) = 72 + (57 + 57 + 57) = 243

As you can see, tknk's disc is unbalanced: ugml's stack is heavier than the other two. Even though the nodes above ugml are balanced, ugml itself is too heavy: it needs to be 8 units lighter for its stack to weigh 243 and keep the towers balanced. If this change were made, its weight would be 60.

Given that exactly one program is the wrong weight, what would its weight need to be to balance the entire tower?


In [47]:
holding = {}
weights = {}
with open('data07.txt','r') as f:
    for line in f:
        count = 0
        hold = set()
        for word in line.split():
            if(count == 0):
                name = word
            elif(count == 1):
                weights[name] = int(word.strip('()'))
            else:
                if(len(word) is not 2):
                    hold.add(word.strip(','))

            count += 1
        if(len(hold) > 0):
            holding[name] = hold
name = ""

for x in holding.keys():
    found = False
    for y in holding.values():
        if x in y:
            found = True
            break

    if not found:
        name = x
        break
print("Part one: %s" % name)

def totalWeight(label):
    totW = 0

    subs = {}
    for sub in holding[label]:
        if(sub in holding):
            subs[sub] = totalWeight(sub)
        else:
            subs[sub] = weights[sub]

    x = 0
    s = next(iter(subs))
    w = subs[s]
    for s_ in subs:
        if(subs[s_] is not w):
            x += subs[s_] - w
            if (x is not 0):
                subs[s_] -= x
                weights[s_] -= x
                print("Part two:", weights[s_])

    for s in subs:
        totW += subs[s]
    totW += weights[label]

    return totW
totalWeight('ahnofa')


Part one: ahnofa
Part two: 802
Out[47]:
419257

--- Day 8: I Heard You Like Registers ---

You receive a signal directly from the CPU. Because of your recent assistance with jump instructions, it would like you to compute the result of a series of unusual register instructions.

Each instruction consists of several parts: the register to modify, whether to increase or decrease that register's value, the amount by which to increase or decrease it, and a condition. If the condition fails, skip the instruction without modifying the register. The registers all start at 0. The instructions look like this:

b inc 5 if a > 1 a inc 1 if b < 5 c dec -10 if a >= 1 c inc -20 if c == 10

These instructions would be processed as follows:

Because a starts at 0, it is not greater than 1, and so b is not modified.
a is increased by 1 (to 1) because b is less than 5 (it is 0).
c is decreased by -10 (to 10) because a is now greater than or equal to 1 (it is 1).
c is increased by -20 (to -10) because c is equal to 10.

After this process, the largest value in any register is 1.

You might also encounter <= (less than or equal to) or != (not equal to). However, the CPU doesn't have the bandwidth to tell you what all the registers are named, and leaves that to you to determine.

What is the largest value in any register after completing the instructions in your puzzle input? --- Part Two ---

To be safe, the CPU also needs to know the highest value held in any register during this process so that it can decide how much memory to allocate to these operations. For example, in the above instructions, the highest value ever held was 10 (in register c after the third instruction was evaluated).

Your puzzle answer was 5035.


In [48]:
input = []
highestValue = 0
with open('data08.txt','r') as f:
    values= {}
    for line in f:
        name1, modify, value1, _, name2, sign, value2 = line.split(" ")
        value1 = int(value1)
        value2 = int(value2)

        if(name2 not in values):
            values[name2] = 0
        if(name1 not in values):
            values[name1] = 0

        if(eval(str(values[name2]) + sign + str(value2)) == True):
            if(modify == "inc"):
                values[name1] += value1
            else:
                values[name1] -= value1
        if(values[name1] > highestValue):
            highestValue = values[name1]

max = 0
for n in values:
    if(values[n] > max):
        max = values[n]
print("Part one: %d" % max)
print("Part two: %d" % highestValue)
Your puzzle answer was 3880.

The first half of this puzzle is complete! It provides one gold star: *
--- Part Two ---

To be safe, the CPU also needs to know the highest value held in any register
during this process so that it can decide how much memory to allocate to these operations. 
For example, in the above instructions, the highest value ever held was 10 (in register c after
the third instruction was evaluated).


Part one: 3880
Part two: 5035

--- Day 10: Knot Hash ---

You come across some programs that are trying to implement a software emulation of a hash based on knot-tying. The hash these programs are implementing isn't very strong, but you decide to help them anyway. You make a mental note to remind the Elves later not to invent their own cryptographic functions.

This hash function simulates tying a knot in a circle of string with 256 marks on it. Based on the input to be hashed, the function repeatedly selects a span of string, brings the ends together, and gives the span a half-twist to reverse the order of the marks within it. After doing this many times, the order of the marks is used to build the resulting hash.

4--5 pinch 4 5 4 1 / \ 5,0,1 / \/ \ twist / \ / \ 3 0 --> 3 0 --> 3 X 0 \ / \ /\ / \ / \ / 2--1 2 1 2 5

To achieve this, begin with a list of numbers from 0 to 255, a current position which begins at 0 (the first element in the list), a skip size (which starts at 0), and a sequence of lengths (your puzzle input). Then, for each length:

Reverse the order of that length of elements in the list, starting with the element at the current position.
Move the current position forward by that length plus the skip size.
Increase the skip size by one.

The list is circular; if the current position and the length try to reverse elements beyond the end of the list, the operation reverses using as many extra elements as it needs from the front of the list. If the current position moves past the end of the list, it wraps around to the front. Lengths larger than the size of the list are invalid.

Here's an example using a smaller list:

Suppose we instead only had a circular list containing five elements, 0, 1, 2, 3, 4, and were given input lengths of 3, 4, 1, 5.

The list begins as [0] 1 2 3 4 (where square brackets indicate the current position).
The first length, 3, selects ([0] 1 2) 3 4 (where parentheses indicate the sublist to be reversed).
After reversing that section (0 1 2 into 2 1 0), we get ([2] 1 0) 3 4.
Then, the current position moves forward by the length, 3, plus the skip size, 0: 2 1 0 [3] 4. Finally, the skip size increases to 1.

The second length, 4, selects a section which wraps: 2 1) 0 ([3] 4.
The sublist 3 4 2 1 is reversed to form 1 2 4 3: 4 3) 0 ([1] 2.
The current position moves forward by the length plus the skip size, a total of 5, causing it not to move because it wraps around: 4 3 0 [1] 2. The skip size increases to 2.

The third length, 1, selects a sublist of a single element, and so reversing it has no effect.
The current position moves forward by the length (1) plus the skip size (2): 4 [3] 0 1 2. The skip size increases to 3.

The fourth length, 5, selects every element starting with the second: 4) ([3] 0 1 2. Reversing this sublist (3 0 1 2 4 into 4 2 1 0 3) produces: 3) ([4] 2 1 0.
Finally, the current position moves forward by 8: 3 4 2 1 [0]. The skip size increases to 4.

In this example, the first two numbers in the list end up being 3 and 4; to check the process, you can multiply them together to produce 12.

However, you should instead use the standard list size of 256 (with values 0 to 255) and the sequence of lengths in your puzzle input. Once this process is complete, what is the result of multiplying the first two numbers in the list? Your puzzle answer was 2928.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

The logic you've constructed forms a single round of the Knot Hash algorithm; running the full thing requires many of these rounds. Some input and output processing is also required.

First, from now on, your input should be taken not as a list of numbers, but as a string of bytes instead. Unless otherwise specified, convert characters to bytes using their ASCII codes. This will allow you to handle arbitrary ASCII strings, and it also ensures that your input lengths are never larger than 255. For example, if you are given 1,2,3, you should convert it to the ASCII codes for each character: 49,44,50,44,51.

Once you have determined the sequence of lengths to use, add the following lengths to the end of the sequence: 17, 31, 73, 47, 23. For example, if you are given 1,2,3, your final sequence of lengths should be 49,44,50,44,51,17,31,73,47,23 (the ASCII codes from the input string combined with the standard length suffix values).

Second, instead of merely running one round like you did above, run a total of 64 rounds, using the same length sequence in each round. The current position and skip size should be preserved between rounds. For example, if the previous example was your first round, you would start your second round with the same length sequence (3, 4, 1, 5, 17, 31, 73, 47, 23, now assuming they came from ASCII codes and include the suffix), but start with the previous round's current position (4) and skip size (4).

Once the rounds are complete, you will be left with the numbers from 0 to 255 in some order, called the sparse hash. Your next task is to reduce these to a list of only 16 numbers called the dense hash. To do this, use numeric bitwise XOR to combine each consecutive block of 16 numbers in the sparse hash (there are 16 such blocks in a list of 256 numbers). So, the first element in the dense hash is the first sixteen elements of the sparse hash XOR'd together, the second element in the dense hash is the second sixteen elements of the sparse hash XOR'd together, etc.

For example, if the first sixteen elements of your sparse hash are as shown below, and the XOR operator is ^, you would calculate the first output number like this:

65 ^ 27 ^ 9 ^ 1 ^ 4 ^ 3 ^ 40 ^ 50 ^ 91 ^ 7 ^ 6 ^ 0 ^ 2 ^ 5 ^ 68 ^ 22 = 64

Perform this operation on each of the sixteen blocks of sixteen numbers in your sparse hash to determine the sixteen numbers in your dense hash.

Finally, the standard way to represent a Knot Hash is as a single hexadecimal string; the final output is the dense hash in hexadecimal notation. Because each number in your dense hash will be between 0 and 255 (inclusive), always represent each number as two hexadecimal digits (including a leading zero as necessary). So, if your first three numbers are 64, 7, 255, they correspond to the hexadecimal numbers 40, 07, ff, and so the first six characters of the hash would be 4007ff. Because every Knot Hash is sixteen such numbers, the hexadecimal representation is always 32 hexadecimal digits (0-f) long.

Here are some example hashes:

The empty string becomes a2582a3a0e66e6e86e3812dcb672a272.
AoC 2017 becomes 33efeb34ea91902bb2f59c9920caa6cd.
1,2,3 becomes 3efbe78a8d82f29979031a4aa0b16a9d.
1,2,4 becomes 63960835bcdc130f0b66d7ff4f6a5a8e.

Treating your puzzle input as a string of ASCII characters, what is the Knot Hash of your puzzle input? Ignore any leading or trailing whitespace you might encounter.


In [3]:
input = [230,1,2,221,97,252,168,169,57,99,0,254,181,255,235,167]

list = []
for x in range(256):
    list.append(x)

inputIndex = 0

index = 0
skipsize = 0

for i in input:
    length = input[inputIndex]
    sublist = []
    for y in range(length):
        step = index + y
        while(step >= len(list)):
            step -= len(list)
        sublist.append(list[step])
    sublist.reverse()

    for z in range(length):
        lstep = index + z
        while(lstep >= len(list)):
            lstep -= len(list)
        list[lstep] = sublist[z]

    index += length + skipsize
    while(index >= len(list)):
        index -= len(list)
    inputIndex += 1
    skipsize += 1

mul = list[0]*list[1]

#---------------------------------------#
print("Part one: %d" %  mul)


Part one: 2928

In [1]:
input = ""
with open('data10.txt','r') as f:
    for line in f:
        for word in line.split():
            input += word

lengths = []

for c in range(len(input)):
    lengths.append(ord(input[c]))

for x in [17, 31, 73, 47, 23]:
    lengths.append(x)

input = lengths

list = []
for x in range(256):
    list.append(x)

index = 0
skipsize = 0

rounds = 0
while(rounds < 64):
    inputIndex = 0

    for i in input:
        length = input[inputIndex]
        sublist = []
        for y in range(length):
            step = index + y
            while (step >= len(list)):
                step -= len(list)
            sublist.append(list[step])
        sublist.reverse()

        for z in range(length):
            lstep = index + z
            while (lstep >= len(list)):
                lstep -= len(list)
            list[lstep] = sublist[z]

        index += length + skipsize
        while (index >= len(list)):
            index -= len(list)
        inputIndex += 1
        skipsize += 1
    rounds += 1

xorlist = []
for x in range(16):
    xor = 0
    for y in range(16):
        xor = xor ^ list[y + x*16]
    xorlist.append(xor)

hexlist = []
for x in xorlist:
    hexlist.append(hex(x)[2:].rjust(2, "0"))

hex = ""
for x in hexlist:
    hex += x

#---------------------------------------#
print("Part two: %s" % hex)


Part two: 0c2f794b2eb555f7830766bf8fb65a16

-- Day 11: Hex Ed ---

Crossing the bridge, you've barely reached the other side of the stream when a program comes up to you, clearly in distress. "It's my child process," she says, "he's gotten lost in an infinite grid!"

Fortunately for her, you have plenty of experience with infinite grids.

Unfortunately for you, it's a hex grid.

The hexagons ("hexes") in this grid are aligned such that adjacent hexes can be found to the north, northeast, southeast, south, southwest, and northwest:

\ n / nw +--+ ne / \ -+ +- \ / sw +--+ se / s \

You have the path the child process took. Starting where he started, you need to determine the fewest number of steps required to reach him. (A "step" means to move from the hex you are in to any adjacent hex.)

For example:

ne,ne,ne is 3 steps away.
ne,ne,sw,sw is 0 steps away (back where you started).
ne,ne,s,s is 2 steps away (se,se).
se,sw,se,sw,sw is 3 steps away (s,s,sw).

Your puzzle answer was 764.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

How many steps away is the furthest he ever got from his starting position?


In [5]:
input = []
with open('data11.txt','r') as f:
    for line in f:
        for word in line.split(','):
            input.append(word)

xCoord = 0
yCoord = 0

steps = 0

for i in input:
    if(len(i) == 1):
        if(i == "n"):
            xCoord += 1
            yCoord -= 1
        elif(i == "s"):
            xCoord -= 1
            yCoord += 1
    else:
        if(i == "ne"):
            xCoord += 1
        elif(i == "se"):
            yCoord += 1
        elif(i == "sw"):
            xCoord -= 1
        elif(i == "nw"):
            yCoord -= 1

    if(abs(xCoord) > steps):
        steps = abs(xCoord)
    elif(abs(yCoord) > steps):
        steps = abs(yCoord)

if(abs(xCoord) > abs(yCoord)):
    solution = abs(xCoord)
else:
    solution = abs(yCoord)

#---------------------------------------#
print("Part one: %d" % solution)

#---------------------------------------#
print("Part two: %d" % steps)


Part one: 765
Part two: 1532

--- Day 12: Digital Plumber ---

Walking along the memory banks of the stream, you find a small village that is experiencing a little confusion: some programs can't communicate with each other.

Programs in this village communicate using a fixed system of pipes. Messages are passed between programs using these pipes, but most programs aren't connected to each other directly. Instead, programs pass messages between each other until the message reaches the intended recipient.

For some reason, though, some of these messages aren't ever reaching their intended recipient, and the programs suspect that some pipes are missing. They would like you to investigate.

You walk through the village and record the ID of each program and the IDs with which it can communicate directly (your puzzle input). Each program has one or more programs with which it can communicate, and these pipes are bidirectional; if 8 says it can communicate with 11, then 11 will say it can communicate with 8.

You need to figure out how many programs are in the group that contains program ID 0.

For example, suppose you go door-to-door like a travelling salesman and record the following list:

0 <-> 2 1 <-> 1 2 <-> 0, 3, 4 3 <-> 2, 4 4 <-> 2, 3, 6 5 <-> 6 6 <-> 4, 5

In this example, the following programs are in the group that contains program ID 0:

Program 0 by definition.
Program 2, directly connected to program 0.
Program 3 via program 2.
Program 4 via program 2.
Program 5 via programs 6, then 4, then 2.
Program 6 via programs 4, then 2.

Therefore, a total of 6 programs are in this group; all but program 1, which has a pipe that connects it to itself.

How many programs are in the group that contains program ID 0? Your puzzle answer was 130.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

There are more programs than just the ones in the group containing program ID 0. The rest of them have no way of reaching that group, and still might have no way of reaching each other.

A group is a collection of programs that can all communicate via pipes either directly or indirectly. The programs you identified just a moment ago are all part of the same group. Now, they would like you to determine the total number of groups.

In the example above, there were 2 groups: one consisting of programs 0,2,3,4,5,6, and the other consisting solely of program 1.

How many groups are there in total?


In [7]:
input = []
with open('data12.txt','r') as f:
    for line in f:
        lines = []
        for word in line.split():
            lines.append(word.strip(','))
        input.append(lines)

def connect(index):
    global group
    a = []
    for x in input[index][2:]:
        a.append(int(x))
    for i in a:
        if(i not in group):
            group += [i]
            connect(i)

connections = []
for y in range(len(input)):
    connections.append(-1)

for i in range(len(input)):
    if(connections[i] == -1):
        group = [i]
        connect(i)
        for j in group:
            connections[j] = i

#---------------------------------------#
print("Part one: %d" % connections.count(0))

#---------------------------------------#
print('Part two: %d' % len(set(connections)))


Part one: 130
Part two: 189

--- Day 13: Packet Scanners ---

You need to cross a vast firewall. The firewall consists of several layers, each with a security scanner that moves back and forth across the layer. To succeed, you must not be detected by a scanner.

By studying the firewall briefly, you are able to record (in your puzzle input) the depth of each layer and the range of the scanning area for the scanner within it, written as depth: range. Each layer has a thickness of exactly 1. A layer at depth 0 begins immediately inside the firewall; a layer at depth 1 would start immediately after that.

For example, suppose you've recorded the following:

0: 3 1: 2 4: 4 6: 4

This means that there is a layer immediately inside the firewall (with range 3), a second layer immediately after that (with range 2), a third layer which begins at depth 4 (with range 4), and a fourth layer which begins at depth 6 (also with range 4). Visually, it might look like this:

0 1 2 3 4 5 6 [ ] [ ] ... ... [ ] ... [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

Within each layer, a security scanner moves back and forth within its range. Each security scanner starts at the top and moves down until it reaches the bottom, then moves up until it reaches the top, and repeats. A security scanner takes one picosecond to move one step. Drawing scanners as S, the first few picoseconds look like this:

Picosecond 0: 0 1 2 3 4 5 6 [S] [S] ... ... [S] ... [S] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

Picosecond 1: 0 1 2 3 4 5 6 [ ] [ ] ... ... [ ] ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

Picosecond 2: 0 1 2 3 4 5 6 [ ] [S] ... ... [ ] ... [ ] [ ] [ ] [ ] [ ] [S] [S] [S] [ ] [ ]

Picosecond 3: 0 1 2 3 4 5 6 [ ] [ ] ... ... [ ] ... [ ] [S] [S] [ ] [ ] [ ] [ ] [ ] [S] [S]

Your plan is to hitch a ride on a packet about to move through the firewall. The packet will travel along the top of each layer, and it moves at one layer per picosecond. Each picosecond, the packet moves one layer forward (its first move takes it into layer 0), and then the scanners move one step. If there is a scanner at the top of the layer as your packet enters it, you are caught. (If a scanner moves into the top of its layer while you are there, you are not caught: it doesn't have time to notice you before you leave.) If you were to do this in the configuration above, marking your current position with parentheses, your passage through the firewall would look like this:

Initial state: 0 1 2 3 4 5 6 [S] [S] ... ... [S] ... [S] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

Picosecond 0: 0 1 2 3 4 5 6 (S) [S] ... ... [S] ... [S] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

0 1 2 3 4 5 6 ( ) [ ] ... ... [ ] ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

Picosecond 1: 0 1 2 3 4 5 6 [ ] ( ) ... ... [ ] ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

0 1 2 3 4 5 6 [ ] (S) ... ... [ ] ... [ ] [ ] [ ] [ ] [ ] [S] [S] [S] [ ] [ ]

Picosecond 2: 0 1 2 3 4 5 6 [ ] [S] (.) ... [ ] ... [ ] [ ] [ ] [ ] [ ] [S] [S] [S] [ ] [ ]

0 1 2 3 4 5 6 [ ] [ ] (.) ... [ ] ... [ ] [S] [S] [ ] [ ] [ ] [ ] [ ] [S] [S]

Picosecond 3: 0 1 2 3 4 5 6 [ ] [ ] ... (.) [ ] ... [ ] [S] [S] [ ] [ ] [ ] [ ] [ ] [S] [S]

0 1 2 3 4 5 6 [S] [S] ... (.) [ ] ... [ ] [ ] [ ] [ ] [ ] [ ] [S] [S] [ ] [ ]

Picosecond 4: 0 1 2 3 4 5 6 [S] [S] ... ... ( ) ... [ ] [ ] [ ] [ ] [ ] [ ] [S] [S] [ ] [ ]

0 1 2 3 4 5 6 [ ] [ ] ... ... ( ) ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

Picosecond 5: 0 1 2 3 4 5 6 [ ] [ ] ... ... [ ] (.) [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

0 1 2 3 4 5 6 [ ] [S] ... ... [S] (.) [S] [ ] [ ] [ ] [ ] [S] [ ] [ ] [ ] [ ]

Picosecond 6: 0 1 2 3 4 5 6 [ ] [S] ... ... [S] ... (S) [ ] [ ] [ ] [ ] [S] [ ] [ ] [ ] [ ]

0 1 2 3 4 5 6 [ ] [ ] ... ... [ ] ... ( ) [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

In this situation, you are caught in layers 0 and 6, because your packet entered the layer when its scanner was at the top when you entered it. You are not caught in layer 1, since the scanner moved into the top of the layer once you were already there.

The severity of getting caught on a layer is equal to its depth multiplied by its range. (Ignore layers in which you do not get caught.) The severity of the whole trip is the sum of these values. In the example above, the trip severity is 03 + 64 = 24.

Given the details of the firewall you've recorded, if you leave immediately, what is the severity of your whole trip? Your puzzle answer was 1580.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

Now, you need to pass through the firewall without being caught - easier said than done.

You can't control the speed of the packet, but you can delay it any number of picoseconds. For each picosecond you delay the packet before beginning your trip, all security scanners move one step. You're not in the firewall during this time; you don't enter layer 0 until you stop delaying the packet.

In the example above, if you delay 10 picoseconds (picoseconds 0 - 9), you won't get caught:

State after delaying: 0 1 2 3 4 5 6 [ ] [S] ... ... [ ] ... [ ] [ ] [ ] [ ] [ ] [S] [S] [S] [ ] [ ]

Picosecond 10: 0 1 2 3 4 5 6 ( ) [S] ... ... [ ] ... [ ] [ ] [ ] [ ] [ ] [S] [S] [S] [ ] [ ]

0 1 2 3 4 5 6 ( ) [ ] ... ... [ ] ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

Picosecond 11: 0 1 2 3 4 5 6 [ ] ( ) ... ... [ ] ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

0 1 2 3 4 5 6 [S] (S) ... ... [S] ... [S] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

Picosecond 12: 0 1 2 3 4 5 6 [S] [S] (.) ... [S] ... [S] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

0 1 2 3 4 5 6 [ ] [ ] (.) ... [ ] ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

Picosecond 13: 0 1 2 3 4 5 6 [ ] [ ] ... (.) [ ] ... [ ] [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

0 1 2 3 4 5 6 [ ] [S] ... (.) [ ] ... [ ] [ ] [ ] [ ] [ ] [S] [S] [S] [ ] [ ]

Picosecond 14: 0 1 2 3 4 5 6 [ ] [S] ... ... ( ) ... [ ] [ ] [ ] [ ] [ ] [S] [S] [S] [ ] [ ]

0 1 2 3 4 5 6 [ ] [ ] ... ... ( ) ... [ ] [S] [S] [ ] [ ] [ ] [ ] [ ] [S] [S]

Picosecond 15: 0 1 2 3 4 5 6 [ ] [ ] ... ... [ ] (.) [ ] [S] [S] [ ] [ ] [ ] [ ] [ ] [S] [S]

0 1 2 3 4 5 6 [S] [S] ... ... [ ] (.) [ ] [ ] [ ] [ ] [ ] [ ] [S] [S] [ ] [ ]

Picosecond 16: 0 1 2 3 4 5 6 [S] [S] ... ... [ ] ... ( ) [ ] [ ] [ ] [ ] [ ] [S] [S] [ ] [ ]

0 1 2 3 4 5 6 [ ] [ ] ... ... [ ] ... ( ) [S] [S] [S] [S] [ ] [ ] [ ] [ ] [ ]

Because all smaller delays would get you caught, the fewest number of picoseconds you would need to delay to get through safely is 10.

What is the fewest number of picoseconds that you need to delay the packet to pass through the firewall without being caught?


In [9]:
input = {}
with open('data13.txt','r') as f:
    for line in f:
        line = line.strip().split(": ")
        input[int(line[0])] = int(line[1])

size = 99
severity = 0
firewall = []
for x in range(size):
    firewall.append([])
    if(x not in input):
        input[x] = 0
        firewall[x].append(0)
    for y in range(input[x]):
        if(y == 0):
            firewall[x].append(1)
        else:
            firewall[x].append(0)

current = 0
while(current < size):
    if(firewall[current][0] == 1 or firewall[current][0] == -1):
        severity += current*len(firewall[current])

    for x in range(size):
        changed = False
        for y in range(len(firewall[x])):
            if((firewall[x][y] == 1 or firewall[x][y] == -1) and not changed):
                if(firewall[x][y] == 1):
                    if(y + 1 < len(firewall[x])):
                        firewall[x][y + 1] = 1
                    else:
                        firewall[x][y - 1] = -1
                else:
                    if(y - 1 >= 0):
                        firewall[x][y - 1] = -1
                    else:
                        firewall[x][y + 1] = 1
                firewall[x][y] = 0
                changed = True
    current += 1
print("Part one: %d" % severity)


Part one: 1580

In [12]:
input = {}
size = 0
with open('data13.txt','r') as f:
    for line in f:
        line = line.strip().split(": ")
        input[int(line[0])] = int(line[1])
        size = int(line[0]) + 1

depths = []
for x in range(size):
    if(x not in input):
        input[x] = 0
    depths.append(input[x])

delay = 0
while True:
    for x in range(size):
        if(depths[x] == 0):
            continue
        if((x + delay) % (depths[x]*2 - 2) == 0):
            break
    else:
        break
    delay += 1

print("Part two: %d" % delay)


Part two: 3943252

--- Day 14: Disk Defragmentation ---

Suddenly, a scheduled job activates the system's disk defragmenter. Were the situation different, you might sit and watch it for a while, but today, you just don't have that kind of time. It's soaking up valuable system resources that are needed elsewhere, and so the only option is to help it finish its task as soon as possible.

The disk in question consists of a 128x128 grid; each square of the grid is either free or used. On this disk, the state of the grid is tracked by the bits in a sequence of knot hashes.

A total of 128 knot hashes are calculated, each corresponding to a single row in the grid; each hash contains 128 bits which correspond to individual grid squares. Each bit of a hash indicates whether that square is free (0) or used (1).

The hash inputs are a key string (your puzzle input), a dash, and a number from 0 to 127 corresponding to the row. For example, if your key string were flqrgnkx, then the first row would be given by the bits of the knot hash of flqrgnkx-0, the second row from the bits of the knot hash of flqrgnkx-1, and so on until the last row, flqrgnkx-127.

The output of a knot hash is traditionally represented by 32 hexadecimal digits; each of these digits correspond to 4 bits, for a total of 4 * 32 = 128 bits. To convert to bits, turn each hexadecimal digit to its equivalent binary value, high-bit first: 0 becomes 0000, 1 becomes 0001, e becomes 1110, f becomes 1111, and so on; a hash that begins with a0c2017... in hexadecimal would begin with 10100000110000100000000101110000... in binary.

Continuing this process, the first 8 rows and columns for key flqrgnkx appear as follows, using # to denote used squares, and . to denote free ones:

.#.#..-->

.#.#.#.#
....#.#.

.#.##.

.##.#...

..#..

.#...#..

.#.##.-->

| |
V V

In this example, 8108 squares are used across the entire 128x128 grid.

Given your actual key string, how many squares are used?

Your puzzle input is wenycdww. Your puzzle answer was 8226.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

Now, all the defragmenter needs to know is the number of regions. A region is a group of used squares that are all adjacent, not including diagonals. Every used square is in exactly one region: lone used squares form their own isolated regions, while several adjacent squares all count as a single region.

In the example above, the following nine regions are visible, each marked with a distinct digit:

11.2.3..--> .1.2.3.4
....5.6.
7.8.55.9
.88.5...
88..5..8
.8...8..
88.8.88.--> | |
V V

Of particular interest is the region marked 8; while it does not appear contiguous in this small view, all of the squares marked 8 are connected when considering the whole 128x128 grid. In total, in this example, 1242 regions are present.

How many regions are present given your key string?


In [29]:
input = "wenycdww"

def knot_hash(lengths):
    list = []
    for x in range(256):
        list.append(x)

    inputIndex = 0

    index = 0
    skipsize = 0

    rounds = 0
    while (rounds < 64):
        inputIndex = 0

        for i in lengths:
            length = lengths[inputIndex]
            sublist = []
            for y in range(length):
                step = index + y
                while (step >= len(list)):
                    step -= len(list)
                sublist.append(list[step])
            sublist.reverse()

            for z in range(length):
                lstep = index + z
                while (lstep >= len(list)):
                    lstep -= len(list)
                list[lstep] = sublist[z]

            index += length + skipsize
            while (index >= len(list)):
                index -= len(list)
            inputIndex += 1
            skipsize += 1
        rounds += 1

    xorlist = []
    for x in range(16):
        xor = 0
        for y in range(16):
            xor = xor ^ list[y + x * 16]
        xorlist.append(xor)

    hexlist = []
    
    for x in xorlist:
        hexlist.append(hex(int(x))[2:].rjust(2, "0"))

    tothex = ""
    for x in hexlist:
        tothex += x

    return tothex

list = []
for x in range(128):
    row = []
    for y in input:
        row.append((ord(y)))
    row.append((ord("-")))
    for z in str(x):
        row.append(ord(z))
    row += [17, 31, 73, 47, 23]
    list.append(row)


blist = []
for x in range(len(list)):
    h = knot_hash(list[x])
    row = []
    for y in h:
        b = bin(int(y, 16))[2:].zfill(4)
        for l in range(len(b)):
            row.append(int(b[l]))
    blist.append(row)

sum = 0
for x in blist:
    for y in x:
        sum += int(y)

def regions(i, j):
    if(i < 0 or j < 0 or i >= len(blist) or j >= len(blist[i]) or blist[i][j] == 0):
        return 0
    else:
        blist[i][j] = 0
        regions(i + 1, j)
        regions(i - 1, j)
        regions(i, j + 1)
        regions(i, j - 1)
        return 1

sumr = 0
for i in range(len(blist)):
    for j in range(len(blist[i])):
        sumr += regions(i, j)

# Correct answer: 8226

#---------------------------------------#
print("Part two: %d" % sumr)

# Correct answer: 1128


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-29-79bf8bc5d7b9> in <module>()
     70 blist = []
     71 for x in range(len(list)):
---> 72     h = knot_hash(list[x])
     73     row = []
     74     for y in h:

<ipython-input-29-79bf8bc5d7b9> in knot_hash(lengths)
     48 
     49     for x in xorlist:
---> 50         hexlist.append(hex(int(x))[2:].rjust(2, "0"))
     51 
     52     tothex = ""

TypeError: 'str' object is not callable

--- Day 15: Dueling Generators ---

Here, you encounter a pair of dueling generators. The generators, called generator A and generator B, are trying to agree on a sequence of numbers. However, one of them is malfunctioning, and so the sequences don't always match.

As they do this, a judge waits for each of them to generate its next value, compares the lowest 16 bits of both values, and keeps track of the number of times those parts of the values match.

The generators both work on the same principle. To create its next value, a generator will take the previous value it produced, multiply it by a factor (generator A uses 16807; generator B uses 48271), and then keep the remainder of dividing that resulting product by 2147483647. That final remainder is the value it produces next.

To calculate each generator's first value, it instead uses a specific starting value as its "previous value" (as listed in your puzzle input).

For example, suppose that for starting values, generator A uses 65, while generator B uses 8921. Then, the first five pairs of generated values are:

--Gen. A-- --Gen. B-- 1092455 430625591 1181022009 1233683848 245556042 1431495498 1744312007 137874439 1352636452 285222916

In binary, these pairs are (with generator A's value first in each pair):

00000000000100001010101101100111 00011001101010101101001100110111

01000110011001001111011100111001 01001001100010001000010110001000

00001110101000101110001101001010 01010101010100101110001101001010

01100111111110000001011011000111 00001000001101111100110000000111

01010000100111111001100000100100 00010001000000000010100000000100

Here, you can see that the lowest (here, rightmost) 16 bits of the third value match: 1110001101001010. Because of this one match, after processing these five pairs, the judge would have added only 1 to its total.

To get a significant sample, the judge would like to consider 40 million pairs. (In the example above, the judge would eventually find a total of 588 pairs that match in their lowest 16 bits.)

After 40 million pairs, what is the judge's final count? Your puzzle answer was 631.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

In the interest of trying to align a little better, the generators get more picky about the numbers they actually give to the judge.

They still generate values in the same way, but now they only hand a value to the judge when it meets their criteria:

Generator A looks for values that are multiples of 4.
Generator B looks for values that are multiples of 8.

Each generator functions completely independently: they both go through values entirely on their own, only occasionally handing an acceptable value to the judge, and otherwise working through the same sequence of values as before until they find one.

The judge still waits for each generator to provide it with a value before comparing them (using the same comparison method as before). It keeps track of the order it receives values; the first values from each generator are compared, then the second values from each generator, then the third values, and so on.

Using the example starting values given above, the generators now produce the following first five values each:

--Gen. A-- --Gen. B-- 1352636452 1233683848 1992081072 862516352 530830436 1159784568 1980017072 1616057672 740335192 412269392

These values have the following corresponding binary values:

01010000100111111001100000100100 01001001100010001000010110001000

01110110101111001011111010110000 00110011011010001111010010000000

00011111101000111101010001100100 01000101001000001110100001111000

01110110000001001010100110110000 01100000010100110001010101001000

00101100001000001001111001011000 00011000100100101011101101010000

Unfortunately, even though this change makes more bits similar on average, none of these values' lowest 16 bits match. Now, it's not until the 1056th pair that the judge finds the first match:

--Gen. A-- --Gen. B-- 1023762912 896885216

00111101000001010110000111100000 00110101011101010110000111100000

This change makes the generators much slower, and the judge is getting impatient; it is now only willing to consider 5 million pairs. (Using the values from the example above, after five million pairs, the judge would eventually find a total of 309 pairs that match in their lowest 16 bits.)

After 5 million pairs, but using this new generator logic, what is the judge's final count?


In [30]:
startA = 873
startB = 583

def calc1(it):
    count = 0
    a = startA
    b = startB
    for x in range(it):
        a *= 16807
        a %= 2147483647

        binA = bin(a)[len(bin(a)) - 16:]

        b *= 48271
        b %= 2147483647

        binB = bin(b)[len(bin(b)) - 16:]
        if(binA == binB):
            count += 1
    return count

def calc2(it):
    count = 0
    a = startA
    b = startB
    for x in range(it):
        while True:
            a *= 16807
            a %= 2147483647
            if(a % 4 == 0):
                break
        binA = bin(a)[len(bin(a)) - 16:]
        while True:
            b *= 48271
            b %= 2147483647
            if(b % 8 == 0):
                break
        binB = bin(b)[len(bin(b)) - 16:]
        if (binA == binB):
            count += 1
    return count

print("Part one: %d" % calc1(40000000))
print("Part two: %d" % calc2(5000000))

#! It will take a while...


Part one: 631
Part two: 279

--- Day 16: Permutation Promenade ---

You come upon a very unusual sight; a group of programs here appear to be dancing.

There are sixteen programs in total, named a through p. They start by standing in a line: a stands in position 0, b stands in position 1, and so on until p, which stands in position 15.

The programs' dance consists of a sequence of dance moves:

Spin, written sX, makes X programs move from the end to the front, but maintain their order otherwise. (For example, s3 on abcde produces cdeab).
Exchange, written xA/B, makes the programs at positions A and B swap places.
Partner, written pA/B, makes the programs named A and B swap places.

For example, with only five programs standing in a line (abcde), they could do the following dance:

s1, a spin of size 1: eabcd.
x3/4, swapping the last two programs: eabdc.
pe/b, swapping programs e and b: baedc.

After finishing their dance, the programs end up in order baedc.

You watch the dance for a while and record their dance moves (your puzzle input). In what order are the programs standing after their dance? Your puzzle answer was fnloekigdmpajchb.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

Now that you're starting to get a feel for the dance moves, you turn your attention to the dance as a whole.

Keeping the positions they ended up in from their previous dance, the programs perform it again and again: including the first dance, a total of one billion (1000000000) times.

In the example above, their second dance would begin with the order baedc, and use the same dance moves:

s1, a spin of size 1: cbaed.
x3/4, swapping the last two programs: cbade.
pe/b, swapping programs e and b: ceadb.

In what order are the programs standing after their billion dances?


In [75]:
input = open('data16.txt').read().strip().split(',')

positions = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"]

def move(iterations, pos):
    sequence = []
    for i in range(iterations):
        s = ''.join(pos)
        if s in sequence:
            return sequence[iterations % i]
        sequence.append(s)
        for i in input:
            if i[0] == 's':
                x = int(i[1:])
                pos = pos[-x:] + pos[:-x]
            elif i[0] == 'x':
                x = i[1:].split('/')
                a, b = int(x[0]), int(x[1])
                pos[a], pos[b] = pos[b], pos[a]
            elif i[0] == 'p':
                a, b = i[1:].split('/')
                A = pos.index(a)
                B = pos.index(b)
                pos[A], pos[B] = pos[B], pos[A]
                # pos[A] = pos[B]
                # pos[B] = pos[A] won't work.

    return ''.join(pos)
print(positions[:])
print("Part one: %s" % move(1, positions[:]))
print("Part two: %s" % move(1000000000, positions[:]))


['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']
Part one: fnloekigdmpajchb
Part two: amkjepdhifolgncb

--- Day 18: Duet ---

You discover a tablet containing some strange assembly code labeled simply "Duet". Rather than bother the sound card with it, you decide to run the code yourself. Unfortunately, you don't see any documentation, so you're left to figure out what the instructions mean on your own.

It seems like the assembly is meant to operate on a set of registers that are each named with a single letter and that can each hold a single integer. You suppose each register should start with a value of 0.

There aren't that many instructions, so it shouldn't be hard to figure out what they do. Here's what you determine:

snd X plays a sound with a frequency equal to the value of X.
set X Y sets register X to the value of Y.
add X Y increases register X by the value of Y.
mul X Y sets register X to the result of multiplying the value contained in register X by the value of Y.
mod X Y sets register X to the remainder of dividing the value contained in register X by the value of Y (that is, it sets X to the result of X modulo Y).
rcv X recovers the frequency of the last sound played, but only when the value of X is not zero. (If it is zero, the command does nothing.)
jgz X Y jumps with an offset of the value of Y, but only if the value of X is greater than zero. (An offset of 2 skips the next instruction, an offset of -1 jumps to the previous instruction, and so on.)

Many of the instructions can take either a register (a single letter) or a number. The value of a register is the integer it contains; the value of a number is that number.

After each jump instruction, the program continues with the instruction to which the jump jumped. After any other instruction, the program continues with the next instruction. Continuing (or jumping) off either end of the program terminates it.

For example:

set a 1 add a 2 mul a a mod a 5 snd a set a 0 rcv a jgz a -1 set a 1 jgz a -2

The first four instructions set a to 1, add 2 to it, square it, and then set it to itself modulo 5, resulting in a value of 4.
Then, a sound with frequency 4 (the value of a) is played.
After that, a is set to 0, causing the subsequent rcv and jgz instructions to both be skipped (rcv because a is 0, and jgz because a is not greater than 0).
Finally, a is set to 1, causing the next jgz instruction to activate, jumping back two instructions to another jump, which jumps again to the rcv, which ultimately triggers the recover operation.

At the time the recover operation is executed, the frequency of the last sound played is 4.

What is the value of the recovered frequency (the value of the most recently played sound) the first time a rcv instruction is executed with a non-zero value? Your puzzle answer was 1187.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

As you congratulate yourself for a job well done, you notice that the documentation has been on the back of the tablet this entire time. While you actually got most of the instructions correct, there are a few key differences. This assembly code isn't about sound at all - it's meant to be run twice at the same time.

Each running copy of the program has its own set of registers and follows the code independently - in fact, the programs don't even necessarily run at the same speed. To coordinate, they use the send (snd) and receive (rcv) instructions:

snd X sends the value of X to the other program. These values wait in a queue until that program is ready to receive them. Each program has its own message queue, so a program can never receive a message it sent.
rcv X receives the next value and stores it in register X. If no values are in the queue, the program waits for a value to be sent to it. Programs do not continue to the next instruction until they have received a value. Values are received in the order they are sent.

Each program also has its own program ID (one 0 and the other 1); the register p should begin with this value.

For example:

snd 1 snd 2 snd p rcv a rcv b rcv c rcv d

Both programs begin by sending three values to the other. Program 0 sends 1, 2, 0; program 1 sends 1, 2, 1. Then, each program receives a value (both 1) and stores it in a, receives another value (both 2) and stores it in b, and then each receives the program ID of the other program (program 0 receives 1; program 1 receives 0) and stores it in c. Each program now sees a different value in its own copy of register c.

Finally, both programs try to rcv a fourth time, but no data is waiting for either of them, and they reach a deadlock. When this happens, both programs terminate.

It should be noted that it would be equally valid for the programs to run at different speeds; for example, program 0 might have sent all three values and then stopped at the first rcv before program 1 executed even its first instruction.

Once both of your programs have terminated (regardless of what caused them to do so), how many times did program 1 send a value?


In [76]:
input = []
with open('data18.txt','r') as f:
    for line in f:
        l = line.strip('\n')
        c = l.split()
        cmd = []
        for x in c:
            try:
                cmd.append(int(x))
            except ValueError:
                cmd.append(x)
        input.append(cmd)
r = {'a': 0, 'b': 0, 'f': 0, "i": 0, "l": 0, "p": 0}

send = 0
recover = 0

def getValue(v):
    try:
        return int(v)

    except ValueError:
        return r[v]

cur = 0
while True:
    # It's long, I know, part two is shorter.
    if(input[cur][0] == "snd"):
        value = getValue(input[cur][1])
        sound = value

    elif(input[cur][0] == "set"):
        register = input[cur][1]
        value = getValue(input[cur][2])

        r[register] = value

    elif(input[cur][0] == "add"):
        register = input[cur][1]
        value = getValue(input[cur][2])

        r[register] += value

    elif (input[cur][0] == "mul"):
        register = input[cur][1]
        value = getValue(input[cur][2])

        r[register] *= value

    elif (input[cur][0] == "mod"):
        register = input[cur][1]
        value = getValue(input[cur][2])

        r[register] %= value

    elif (input[cur][0] == "rcv"):
        value = getValue(input[cur][1])
        if(value != 0):
            recover = sound
            break

    elif (input[cur][0] == "jgz"):
        register = input[cur][1]
        value = getValue(input[cur][2])

        if(r[register] > 0):
            cur += (value - 1)
        elif(r[register] < 0):
            cur += value

    cur += 1
    if(cur >= len(input)):
        break

print("Part one: %d" % recover)


Part one: 1187

In [77]:
input = []
with open('data18.txt','r') as f:
    for line in f:
        l = line.strip('\n')
        c = l.split()
        cmd = []
        for x in c:
            try:
                cmd.append(int(x))
            except ValueError:
                cmd.append(x)
        input.append(cmd)
r1 = {"a": 0, "b": 0, "f": 0, "i": 0, "l": 0, "p": 0}
r2 = {"a": 0, "b": 0, "f": 0, "i": 0, "l": 0, "p": 1}

# returns value (int), or value (int) in list with the given name
def getValue(v, r):
    try:
        return int(v)

    except ValueError:
        return r[v]

que1 = []
que2 = []
stop = [False, False]

count = 0
def run(reg, i):
    global count

    # assign correct arrays to variables
    if(reg == 0):
        register = r1
        recover = que1
    else:
        register = r2
        recover = que2

    # out of range
    if(i < 0 or i >= len(input)):
        stop[reg] = True
        return

    # copy the line /w index i from input
    cmd = input[i]

    # send, by adding it to an array (queue)
    if cmd[0] == "snd":
        if reg == 0:
            que2.insert(0, getValue(cmd[1], register))
        else:
            que1.insert(0, getValue(cmd[1], register))
            count += 1

    # needless to say
    elif cmd[0] == "set":
        register[cmd[1]] = getValue(cmd[2], register)
    elif cmd[0] == "add":
        register[cmd[1]] += getValue(cmd[2], register)
    elif cmd[0] == "mul":
        register[cmd[1]] *= getValue(cmd[2], register)
    elif cmd[0] == "mod":
        register[cmd[1]] %= getValue(cmd[2], register)

    # wait or change value
    elif cmd[0] == "rcv":
       if len(recover) == 0:
           stop[reg] = True
           return i
       else:
           stop[reg] = False
           register[cmd[1]] = recover.pop()

    # jump
    elif cmd[0] == "jgz":
        if getValue(cmd[1], register) > 0:
            return i + getValue(cmd[2], register)

# indexes
reg1 = 0
reg2 = 0
while not (stop[0] and stop[1]):
    # program 1
    r = run(0,reg1)
    if r is not None:
        reg1 = r
    else:
        reg1 += 1

    # program 2
    r = run(1,reg2)
    if r is not None:
        reg2 = r
    else:
        reg2 += 1

print("Part two: %d" % count)


Part two: 5969

--- Day 19: A Series of Tubes ---

Somehow, a network packet got lost and ended up here. It's trying to follow a routing diagram (your puzzle input), but it's confused about where to go.

Its starting point is just off the top of the diagram. Lines (drawn with |, -, and +) show the path it needs to take, starting by going down onto the only line connected to the top of the diagram. It needs to follow this path until it reaches the end (located somewhere within the diagram) and stop there.

Sometimes, the lines cross over each other; in these cases, it needs to continue going the same direction, and only turn left or right when there's no other option. In addition, someone has left letters on the line; these also don't change its direction, but it can use them to keep track of where it's been. For example:

 |          
 |  +--+    
 A  |  C    

F---|----E|--+ | | | D +B-+ +--+

Given this diagram, the packet needs to take the following path:

Starting at the only line touching the top of the diagram, it must go down, pass through A, and continue onward to the first +.
Travel right, up, and right, passing through B in the process.
Continue down (collecting C), right, and up (collecting D).
Finally, go all the way left through E and stopping at F.

Following the path to the end, the letters it sees on its path are ABCDEF.

The little packet looks up at you, hoping you can help it find the way. What letters will it see (in the order it would see them) if it follows the path? (The routing diagram is very wide; make sure you view it without line wrapping.) Your puzzle answer was RYLONKEWB.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

The packet is curious how many steps it needs to go.

For example, using the same routing diagram from the example above...

 |          
 |  +--+    
 A  |  C    

F---|--|-E---+ | | | D +B-+ +--+

...the packet would go:

6 steps down (including the first line at the top of the diagram).
3 steps right.
4 steps up.
3 steps right.
4 steps down.
3 steps right.
2 steps up.
13 steps left (including the F it stops on).

This would result in a total of 38 steps.

How many steps does the packet need to go?


In [82]:
input = []
with open('data19.txt', 'r') as f:
    for line in f:
        line = str(line)
        row = []
        for l in range(len(line) - 1):
            row.append(line[l])
        while(len(row) < 201):
            row.append(" ")

        input.append(row)

x = input[0].index("|")
y = 0

letters = ""
current = "|"
direction = "down"

steps = 0

while current != " ":
    steps += 1
    if(direction == "down"):
        y += 1
    elif(direction == "up"):
        y -= 1
    elif(direction == "right"):
        x += 1
    elif(direction == "left"):
        x -= 1

    current = input[y][x]
    if(current == "+"):
        if(direction == "down" or direction == "up"):
            if(input[y][x + 1] != " "):
                direction = "right"
            else:
                direction = "left"
        else:
            if(input[y + 1][x] != " "):
                direction = "down"
            else:
                direction = "up"
    elif(current != "|" and current != "-"):
        letters += current

print("Part one: %s" % letters)
print("Part two: %d" % steps)


Part one: RYLONKEWB 
Part two: 16016

--- Day 20: Particle Swarm ---

Suddenly, the GPU contacts you, asking for help. Someone has asked it to simulate too many particles, and it won't be able to finish them all in time to render the next frame at this rate.

It transmits to you a buffer (your puzzle input) listing each particle in order (starting with particle 0, then particle 1, particle 2, and so on). For each particle, it provides the X, Y, and Z coordinates for the particle's position (p), velocity (v), and acceleration (a), each in the format <X,Y,Z>.

Each tick, all particles are updated simultaneously. A particle's properties are updated in the following order:

Increase the X velocity by the X acceleration.
Increase the Y velocity by the Y acceleration.
Increase the Z velocity by the Z acceleration.
Increase the X position by the X velocity.
Increase the Y position by the Y velocity.
Increase the Z position by the Z velocity.

Because of seemingly tenuous rationale involving z-buffering, the GPU would like to know which particle will stay closest to position <0,0,0> in the long term. Measure this using the Manhattan distance, which in this situation is simply the sum of the absolute values of a particle's X, Y, and Z position.

For example, suppose you are only given two particles, both of which stay entirely on the X-axis (for simplicity). Drawing the current states of particles 0 and 1 (in that order) with an adjacent a number line and diagram of current X positions (marked in parenthesis), the following would take place:

p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0> -4 -3 -2 -1 0 1 2 3 4 p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0> (0)(1)

p=< 4,0,0>, v=< 1,0,0>, a=<-1,0,0> -4 -3 -2 -1 0 1 2 3 4 p=< 2,0,0>, v=<-2,0,0>, a=<-2,0,0> (1) (0)

p=< 4,0,0>, v=< 0,0,0>, a=<-1,0,0> -4 -3 -2 -1 0 1 2 3 4 p=<-2,0,0>, v=<-4,0,0>, a=<-2,0,0> (1) (0)

p=< 3,0,0>, v=<-1,0,0>, a=<-1,0,0> -4 -3 -2 -1 0 1 2 3 4 p=<-8,0,0>, v=<-6,0,0>, a=<-2,0,0> (0)

At this point, particle 1 will never be closer to <0,0,0> than particle 0, and so, in the long run, particle 0 will stay closest.

Which particle will stay closest to position <0,0,0> in the long term? he first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

To simplify the problem further, the GPU would like to remove any particles that collide. Particles collide if their positions ever exactly match. Because particles are updated simultaneously, more than two particles can collide at the same time and place. Once particles collide, they are removed and cannot collide with anything else after that tick.

For example:

p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0>
p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0> -6 -5 -4 -3 -2 -1 0 1 2 3 p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0> (0) (1) (2) (3) p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0>

p=<-3,0,0>, v=< 3,0,0>, a=< 0,0,0>
p=<-2,0,0>, v=< 2,0,0>, a=< 0,0,0> -6 -5 -4 -3 -2 -1 0 1 2 3 p=<-1,0,0>, v=< 1,0,0>, a=< 0,0,0> (0)(1)(2) (3)
p=< 2,0,0>, v=<-1,0,0>, a=< 0,0,0>

p=< 0,0,0>, v=< 3,0,0>, a=< 0,0,0>
p=< 0,0,0>, v=< 2,0,0>, a=< 0,0,0> -6 -5 -4 -3 -2 -1 0 1 2 3 p=< 0,0,0>, v=< 1,0,0>, a=< 0,0,0> X (3)
p=< 1,0,0>, v=<-1,0,0>, a=< 0,0,0>

------destroyed by collision------
------destroyed by collision------ -6 -5 -4 -3 -2 -1 0 1 2 3 ------destroyed by collision------ (3)
p=< 0,0,0>, v=<-1,0,0>, a=< 0,0,0>

In this example, particles 0, 1, and 2 are simultaneously destroyed at the time and place marked X. On the next tick, particle 3 passes through unharmed.

How many particles are left after all collisions are resolved?


In [85]:
input = []
with open('data20.txt', 'r') as f:
    for line in f:
        l = ""
        for char in line:
            if not char in " pva=<>\n":
                l += char
        l = l.split(',')
        line = []
        count = 0
        vector = []
        for x in range(len(l)):
            count += 1
            vector.append(int(l[x]))
            if(count == 3):
                line.append(vector)
                vector = []
                count = 0
        input.append(line)

for c in range(1000):
    for x in range(len(input)):
        for y in range(3):
            input[x][1][y] += input[x][2][y]
            input[x][0][y] += input[x][1][y]


zero = 0
sum = 1000000000
for x in range(len(input)):
    pos = 0
    for y in range(3):
        pos += abs(input[x][0][y])
    if(pos < sum):
        sum = pos
        zero = x

print("Part one: %d" % zero)


Part one: 308

In [86]:
input = []
with open('data20.txt', 'r') as f:
    for line in f:
        l = ""
        for char in line:
            if not char in " pva=<>\n":
                l += char
        l = l.split(',')
        line = []
        count = 0
        vector = []
        for x in range(len(l)):
            count += 1
            vector.append(int(l[x]))
            if(count == 3):
                line.append(vector)
                vector = []
                count = 0
        input.append(line)
for c in range(50):
    for x in range(len(input)):
        for y in range(3):
            input[x][1][y] += input[x][2][y]
            input[x][0][y] += input[x][1][y]

    remove = []
    for x in range(len(input)):
        for z in range(len(input)):
            if(z != x and input[x][0] == input[z][0]):
                if(x not in remove):
                    remove.append(x)
    remove.reverse()
    for x in remove:
        input.pop(x)
print("Part two: %d" % len(input))


Part two: 504

--- Day 22: Sporifica Virus ---

Diagnostics indicate that the local grid computing cluster has been contaminated with the Sporifica Virus. The grid computing cluster is a seemingly-infinite two-dimensional grid of compute nodes. Each node is either clean or infected by the virus.

To prevent overloading the nodes (which would render them useless to the virus) or detection by system administrators, exactly one virus carrier moves through the network, infecting or cleaning nodes as it moves. The virus carrier is always located on a single node in the network (the current node) and keeps track of the direction it is facing.

To avoid detection, the virus carrier works in bursts; in each burst, it wakes up, does some work, and goes back to sleep. The following steps are all executed in order one time each burst:

If the current node is infected, it turns to its right. Otherwise, it turns to its left. (Turning is done in-place; the current node does not change.)
If the current node is clean, it becomes infected. Otherwise, it becomes cleaned. (This is done after the node is considered for the purposes of changing direction.)
The virus carrier moves forward one node in the direction it is facing.

Diagnostics have also provided a map of the node infection status (your puzzle input). Clean nodes are shown as .; infected nodes are shown as #. This map only shows the center of the grid; there are many more nodes beyond those shown, but none of them are currently infected.

The virus carrier begins in the middle of the map facing up.

For example, suppose you are given a map like this:

..#

..

...

Then, the middle of the infinite grid looks like this, with the virus carrier's position marked with [ ]:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . . . #[.]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

The virus carrier is on a clean node, so it turns left, infects the node, and moves left:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . . .[#]# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

The virus carrier is on an infected node, so it turns right, cleans the node, and moves up:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .[.]. # . . . . . . . # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Four times in a row, the virus carrier finds a clean, infects it, turns left, and moves forward, ending in the same place and still facing up:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . #[#]. # . . . . . # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Now on the same node as before, it sees an infection, which causes it to turn right, clean the node, and move forward:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . # .[.]# . . . . . # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

After the above actions, a total of 7 bursts of activity had taken place. Of them, 5 bursts of activity caused an infection.

After a total of 70, the grid looks like this, with the virus carrier facing up:

. . . . . # # . . . . . . # . . # . . . . # . . . . # . . # . #[.]. . # . . # . # . . # . . . . . . # # . . . . . . . . . . . . . . . . . . . .

By this time, 41 bursts of activity caused an infection (though most of those nodes have since been cleaned).

After a total of 10000 bursts of activity, 5587 bursts will have caused an infection.

Given your actual map, after 10000 bursts of activity, how many bursts cause a node to become infected? (Do not count nodes that begin infected.) Your puzzle answer was 5369.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

As you go to remove the virus from the infected nodes, it evolves to resist your attempt.

Now, before it infects a clean node, it will weaken it to disable your defenses. If it encounters an infected node, it will instead flag the node to be cleaned in the future. So:

Clean nodes become weakened.
Weakened nodes become infected.
Infected nodes become flagged.
Flagged nodes become clean.

Every node is always in exactly one of the above states.

The virus carrier still functions in a similar way, but now uses the following logic during its bursts of action:

Decide which way to turn based on the current node:
    If it is clean, it turns left.
    If it is weakened, it does not turn, and will continue moving in the same direction.
    If it is infected, it turns right.
    If it is flagged, it reverses direction, and will go back the way it came.
Modify the state of the current node, as described above.
The virus carrier moves forward one node in the direction it is facing.

Start with the same map (still using . for clean and # for infected) and still with the virus carrier starting in the middle and facing up.

Using the same initial state as the previous example, and drawing weakened as W and flagged as F, the middle of the infinite grid looks like this, with the virus carrier's position again marked with [ ]:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . . . #[.]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

This is the same as before, since no initial nodes are weakened or flagged. The virus carrier is on a clean node, so it still turns left, instead weakens the node, and moves left:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # . . . . . .[#]W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

The virus carrier is on an infected node, so it still turns right, instead flags the node, and moves up:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .[.]. # . . . . . . F W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

This process repeats three more times, ending on the previously-flagged node and facing right:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . W W . # . . . . . W[F]W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Finding a flagged node, it reverses direction and cleans the node:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . W W . # . . . . .[W]. W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

The weakened node becomes infected, and it continues in the same direction:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . W W . # . . . .[.]# . W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Of the first 100 bursts, 26 will result in infection. Unfortunately, another feature of this evolved virus is speed; of the first 10000000 bursts, 2511944 will result in infection.

Given your actual map, after 10000000 bursts of activity, how many bursts cause a node to become infected? (Do not count nodes that begin infected.)


In [87]:
input = []
with open('data22.txt', 'r') as f:
    for line in f:
        l = []
        for c in line.strip('\n'):
            l.append(c)
        input.append(l)

infected = set()
for x in range(len(input[0])):
    for y in range(len(input)):
        if (input[y][x] == '#'):
            infected.add((x, y))

x = len(input[0])//2
y = len(input)//2
v = "u"

count = 0

for _ in range(10000):
    if ((x, y) in infected):
        if (v == "u"): v = "r"
        elif (v == "r"): v = "d"
        elif (v == "d"): v = "l"
        elif (v == "l"): v = "u"
        infected.remove((x, y))
    else:
        if (v == "u"): v = "l"
        elif (v == "r"): v = "u"
        elif (v == "d"): v = "r"
        elif (v == "l"): v = "d"
        infected.add((x, y))
        count += 1

    if(v == "u"): y -= 1
    elif(v == "r"): x += 1
    elif(v == "d"): y += 1
    elif(v == "l"): x -= 1

print("Part one: %d" % count)


Part one: 5369

In [88]:
input = []
with open('data22.txt', 'r') as f:
    for line in f:
        l = []
        for c in line.strip('\n'):
            l.append(c)
        input.append(l)
infected = set()
for x in range(len(input[0])):
    for y in range(len(input)):
        if (input[y][x] == '#'):
            infected.add((x, y))

weakend = set()
flagged = set()

x = len(input[0])//2
y = len(input)//2
v = "u"

count = 0

for _ in range(10000000):
    if ((x, y) in infected):
        if (v == "u"): v = "r"
        elif (v == "r"): v = "d"
        elif (v == "d"): v = "l"
        elif (v == "l"): v = "u"
        flagged.add((x, y))
        infected.remove((x, y))

    elif((x, y) in weakend):
        infected.add((x, y))
        weakend.remove((x, y))
        count += 1

    elif((x, y) in flagged):
        if (v == "u"): v = "d"
        elif (v == "r"): v = "l"
        elif (v == "d"): v = "u"
        elif (v == "l"): v = "r"
        flagged.remove((x, y))

    else:
        if (v == "u"): v = "l"
        elif (v == "r"): v = "u"
        elif (v == "d"): v = "r"
        elif (v == "l"): v = "d"
        weakend.add((x, y))

    if(v == "u"): y -= 1
    elif(v == "r"): x += 1
    elif(v == "d"): y += 1
    elif(v == "l"): x -= 1

print("Part two: %d" % count)


Part two: 2510774

--- Day 23: Coprocessor Conflagration ---

You decide to head directly to the CPU and fix the printer from there. As you get close, you find an experimental coprocessor doing so much work that the local programs are afraid it will halt and catch fire. This would cause serious issues for the rest of the computer, so you head in and see what you can do.

The code it's running seems to be a variant of the kind you saw recently on that tablet. The general functionality seems very similar, but some of the instructions are different:

set X Y sets register X to the value of Y.
sub X Y decreases register X by the value of Y.
mul X Y sets register X to the result of multiplying the value contained in register X by the value of Y.
jnz X Y jumps with an offset of the value of Y, but only if the value of X is not zero. (An offset of 2 skips the next instruction, an offset of -1 jumps to the previous instruction, and so on.)

Only the instructions listed above are used. The eight registers here, named a through h, all start at 0.

The coprocessor is currently set to some kind of debug mode, which allows for testing, but prevents it from doing any meaningful work.

If you run the program (your puzzle input), how many times is the mul instruction invoked? Your puzzle answer was 4225.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

Now, it's time to fix the problem.

The debug mode switch is wired directly to register a. You flip the switch, which makes register a now start at 1 when the program is executed.

Immediately, the coprocessor begins to overheat. Whoever wrote this program obviously didn't choose a very efficient implementation. You'll need to optimize the program if it has any hope of completing before Santa needs that printer working.

The coprocessor's ultimate goal is to determine the final value left in register h once the program completes. Technically, if it had that... it wouldn't even need to run the program.

After setting register a to 1, if the program were to run to completion, what value would be left in register h?


In [5]:
input = []
with open('data23.txt','r') as f:
    for line in f:
        l = line.strip('\n')
        c = l.split()
        cmd = []
        for x in c:
            try:
                cmd.append(int(x))
            except ValueError:
                cmd.append(x)
        input.append(cmd)

r = {'a': 0, 'b': 0, 'c': 0, "d": 0, "e": 0, "f": 0, "g": 0, "h": 0}

def getValue(v):
    try:
        return int(v)

    except ValueError:
        return r[v]

count = 0
cur = 0

steps = 0
while (0  <= cur < len(input)):
    cmd = input[cur]
    c = cmd[0]

    if (c == "jnz"):
        if (getValue(cmd[1]) != 0):
            cur += getValue(cmd[2])
        else:
            cur += 1
    else:
        if(c == "set"):
            r[cmd[1]] = getValue(cmd[2])

        if(c == "sub"):
            r[cmd[1]] -= getValue(cmd[2])

        if (c == "mul"):
            r[cmd[1]] *= getValue(cmd[2])
            count += 1

        cur += 1

        #print(r)

#---------------------------------------#
print("Part one: %d" % count)

h = 0

'''
How to get 107900 & 124900?
If you change a to 1 above:
    r = {'a': 0, 'b': 0, ...} to r = {'a': 1, 'b': 0, ...}
And uncomment #print(r) above and than run the program.
You will see that b gets 'stuck' on a value, in my case: 107900.
The other value is simply b + 17000.

'''

for x in range(106700, 123700 + 1, 17):
    for i in range(2, x):
        if x % i == 0:
            h += 1
            break

'''
"The key to understanding what this code does is starting from the end and working backwards:

    If the program has exited, g had a value of 0 at line 29.
    g==0 at line 29 when b==c.
    If g!=0 at line 29, b increments by 17.
    b increments no other times on the program.
    Thus, lines 25 through 31 will run 1000 times, on values of b increasing by 17, before the program finishes.

So, given that there is no jnz statement between lines 25 and 28 that could affect things:

    If f==0 at line 25, h will increment by 1.
    This can happen once and only once for any given value of b.
    f==0 if g==0 at line 15.
    g==0 at line 15 if d*e==b.
    Since both d and e increment by 1 each in a loop, this will check every possible value of d and e less than b.
    Therefore, if b has any prime factors other than itself, f will be set to 1 at line 25.

Looking at this, then h is the number of composite numbers between the lower limit and the upper limit, counting by 17."

Video by Michael Gilliland: https://www.youtube.com/watch?v=AqXTZo6o34s
'''

#---------------------------------------#
print("Part two: %d" % h)


Part one: 4225
Part two: 905

--- Day 24: Electromagnetic Moat ---

The CPU itself is a large, black building surrounded by a bottomless pit. Enormous metal tubes extend outward from the side of the building at regular intervals and descend down into the void. There's no way to cross, but you need to get inside.

No way, of course, other than building a bridge out of the magnetic components strewn about nearby.

Each component has two ports, one on each end. The ports come in all different types, and only matching types can be connected. You take an inventory of the components by their port types (your puzzle input). Each port is identified by the number of pins it uses; more pins mean a stronger connection for your bridge. A 3/7 component, for example, has a type-3 port on one side, and a type-7 port on the other.

Your side of the pit is metallic; a perfect surface to connect a magnetic, zero-pin port. Because of this, the first port you use must be of type 0. It doesn't matter what type of port you end with; your goal is just to make the bridge as strong as possible.

The strength of a bridge is the sum of the port types in each component. For example, if your bridge is made of components 0/3, 3/7, and 7/4, your bridge has a strength of 0+3 + 3+7 + 7+4 = 24.

For example, suppose you had the following components:

0/2 2/2 2/3 3/4 3/5 0/1 10/1 9/10

With them, you could make the following valid bridges:

0/1
0/1--10/1
0/1--10/1--9/10
0/2
0/2--2/3
0/2--2/3--3/4
0/2--2/3--3/5
0/2--2/2
0/2--2/2--2/3
0/2--2/2--2/3--3/4
0/2--2/2--2/3--3/5

(Note how, as shown by 10/1, order of ports within a component doesn't matter. However, you may only use each port on a component once.)

Of these bridges, the strongest one is 0/1--10/1--9/10; it has a strength of 0+1 + 1+10 + 10+9 = 31.

What is the strength of the strongest bridge you can make with the components you have available? Your puzzle answer was 1695.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

The bridge you've built isn't long enough; you can't jump the rest of the way.

In the example above, there are two longest bridges:

0/2--2/2--2/3--3/4
0/2--2/2--2/3--3/5

Of them, the one which uses the 3/5 component is stronger; its strength is 0+2 + 2+2 + 2+3 + 3+5 = 19.

What is the strength of the longest bridge you can make? If you can make multiple bridges of the longest length, pick the strongest one.


In [91]:
input = []
with open('data24.txt','r') as f:
    for line in f:
        a, b = line.split('/')
        input.append((int(a), int(b)))
bridges = []
strongest = 0
longest = ["", 0]

def port(left, previous, list, strength):
    global strongest
    global longest

    for x in left:
        if(x[0] == previous):
            next = x[1]
            l = left.copy()
            l.remove(x)
            s = list
            s += str(x[0]) + "/" + str(x[1]) + " "
            st = strength
            st += x[0] + x[1]

            # Part one
            if(st > strongest):
                strongest = st
            # Part two
            if(len(s) >= len(longest[0])):
                if(st > longest[1]):
                    longest = [s, st]

            port(l, next, s, st)

        elif(x[1] == previous):
            next = x[0]
            l = left.copy()
            l.remove(x)
            s = list
            s += str(x[0]) + "/" + str(x[1]) + " "
            st = strength
            st += x[0] + x[1]

            # Part one
            if(st > strongest):
                strongest = st
            # Part two
            if(len(s) >= len(longest[0])):
                if(st > longest[1]):
                    longest = [s, st]

            port(l, next, s, st)


left = input.copy()
port(left, 0, "", 0)

print("Part one: %d" % strongest)

print("Part two: %d" % longest[1])


Part one: 1695
Part two: 1673

--- Day 25: The Halting Problem ---

Following the twisty passageways deeper and deeper into the CPU, you finally reach the core of the computer. Here, in the expansive central chamber, you find a grand apparatus that fills the entire room, suspended nanometers above your head.

You had always imagined CPUs to be noisy, chaotic places, bustling with activity. Instead, the room is quiet, motionless, and dark.

Suddenly, you and the CPU's garbage collector startle each other. "It's not often we get many visitors here!", he says. You inquire about the stopped machinery.

"It stopped milliseconds ago; not sure why. I'm a garbage collector, not a doctor." You ask what the machine is for.

"Programs these days, don't know their origins. That's the Turing machine! It's what makes the whole computer work." You try to explain that Turing machines are merely models of computation, but he cuts you off. "No, see, that's just what they want you to think. Ultimately, inside every CPU, there's a Turing machine driving the whole thing! Too bad this one's broken. We're doomed!"

You ask how you can help. "Well, unfortunately, the only way to get the computer running again would be to create a whole new Turing machine from scratch, but there's no way you can-" He notices the look on your face, gives you a curious glance, shrugs, and goes back to sweeping the floor.

You find the Turing machine blueprints (your puzzle input) on a tablet in a nearby pile of debris. Looking back up at the broken Turing machine above, you can start to identify its parts:

A tape which contains 0 repeated infinitely to the left and right.
A cursor, which can move left or right along the tape and read or write values at its current position.
A set of states, each containing rules about what to do based on the current value under the cursor.

Each slot on the tape has two possible values: 0 (the starting value for all slots) and 1. Based on whether the cursor is pointing at a 0 or a 1, the current state says what value to write at the current position of the cursor, whether to move the cursor left or right one slot, and which state to use next.

For example, suppose you found the following blueprint:

Begin in state A. Perform a diagnostic checksum after 6 steps.

In state A: If the current value is 0:

- Write the value 1.
- Move one slot to the right.
- Continue with state B.

If the current value is 1:

- Write the value 0.
- Move one slot to the left.
- Continue with state B.

In state B: If the current value is 0:

- Write the value 1.
- Move one slot to the left.
- Continue with state A.

If the current value is 1:

- Write the value 1.
- Move one slot to the right.
- Continue with state A.

Running it until the number of steps required to take the listed diagnostic checksum would result in the following tape configurations (with the cursor marked in square brackets):

... 0 0 0 [0] 0 0 ... (before any steps; about to run state A) ... 0 0 0 1 [0] 0 ... (after 1 step; about to run state B) ... 0 0 0 [1] 1 0 ... (after 2 steps; about to run state A) ... 0 0 [0] 0 1 0 ... (after 3 steps; about to run state B) ... 0 [0] 1 0 1 0 ... (after 4 steps; about to run state A) ... 0 1 [1] 0 1 0 ... (after 5 steps; about to run state B) ... 0 1 1 [0] 1 0 ... (after 6 steps; about to run state A)

The CPU can confirm that the Turing machine is working by taking a diagnostic checksum after a specific number of steps (given in the blueprint). Once the specified number of steps have been executed, the Turing machine should pause; once it does, count the number of times 1 appears on the tape. In the above example, the diagnostic checksum is 3.

Recreate the Turing machine and save the computer! What is the diagnostic checksum it produces once it's working again? Your puzzle answer was 2474.

The first half of this puzzle is complete! It provides one gold star: * --- Part Two ---

The Turing machine, and soon the entire computer, springs back to life. A console glows dimly nearby, awaiting your command.

reboot printer Error: That command requires priority 50. You currently have priority 0. You must deposit 50 stars to increase your priority to the required level.

The console flickers for a moment, and then prints another message:

Star accepted. You must deposit 49 stars to increase your priority to the required level.

The garbage collector winks at you, then continues sweeping.


In [92]:
input = []
with open('data25.txt','r') as f:
    for line in f:
        input.append(line.strip(" ").strip("\n").lower())
blueprint = {}
state = ""
value = 0
for x in input:
    if(x[:5] == "begin"):
        state = x[len(x) - 2: len(x) - 1]
        blueprint["state"] = state
    elif(x[:7] == "perform"):
        steps = str(x).strip("perform a diagnostic checksum after ").strip(" steps.")
        blueprint["steps"] = int(steps)
    if(x[:2] == "in"):
        state = x[len(x) - 2: len(x) - 1]
        blueprint[state] = {}
    elif(x[:2] == "if"):
        value = int(x[len(x) - 2: len(x) - 1])
    elif(x[:3] == "- w"):
        blueprint[state][value] = [int(x[len(x) - 2: len(x) - 1])]
    elif(x[:3] == "- m"):
        if(x[len(x) - 3: len(x) - 1] == "ft"):
            blueprint[state][value].append(-1)
        else:
            blueprint[state][value].append(1)
    elif(x[:3] == "- c"):
        blueprint[state][value].append(x[len(x) - 2: len(x) - 1])

state = blueprint["state"]
steps = blueprint["steps"]

tape = {}
current = 0

for x in range(steps):
    if (current not in tape):
        tape[current] = 0

    bp = blueprint[state][tape[current]]

    tape[current] = bp[0]
    current += bp[1]
    state = bp[2]

checksum = 0
for x in tape:
    checksum += tape[x]

print("Part one: %d" % checksum)


Part one: 2474

In [ ]: